// 根据不同的指令处理不同的数据 text html model
const compileUtils = {
    // 获取data中属性值值
    getValue (value, vm) {
        // 先以.分割为一个数组，然后使用reduce获取data中的属性值
        return value.split('.').reduce((pre, next) => {
            return pre[next]
        },vm.$data)
    },
    getContentValue (value, vm) {
        return value.replace(/\{\{(.+?)\}\}/g, (...args) => {
            return this.getValue(args[1], vm);
        })
    },
    setValue (vm,value,inputValue) {

        return value.split('.').reduce((pre, next) => {
            if (typeof pre[next] !== 'object') {
                // 如果不是是一个对象直接赋值
                pre[next] = inputValue
            }
            // 是对象 直接取值
            return pre[next]
        },vm.$data)
    },
    text (node , value, vm) { // value可能是{{obj.name}} 可能是 obj.age
        let val;

        if (value.indexOf('{{') !== -1) {
            // 有{{ 说名是 {{obj.name}}
            // 进行全局匹配
            val = value.replace(/\{\{(.+?)\}\}/g, (...args) => {
                // ...args 打印出的三个分别是 当前匹配的值，匹配项在字符串中最小的为止，原始字符串
                new Watcher(vm, args[1], () => {
                    this.updater.textUpdata(node, this.getContentValue(value, vm))
                })
                return this.getValue(args[1], vm)
            })

        } else {
            // obj.age
            val =  this.getValue(value, vm)
        }
        // 更新/替换数据
        this.updater.textUpdata(node, val)
    },
    html (node, value, vm) {
        const val = this.getValue(value, vm)
        new Watcher(vm, value, (newValue) => {
            this.updater.htmlUpdata(node, newValue)
        })
        // 更新/替换数据
        this.updater.htmlUpdata(node,val)
    },
    model (node, value ,vm) {
        const val = this.getValue(value, vm)
        // 数据更新 -> 视图变化
        new Watcher(vm, value, (newValue) => {
            this.updater.modleUpdata(node, newValue)
        })
        // 视图变化 -> 数据更新
        node.addEventListener('input',(e) => {
            this.setValue(vm, value, e.target.value)
        } ,false)
        this.updater.modleUpdata(node, val)
    },
    on (node, value, vm, eventName) {
        // 获取回调函数
        let fn = vm.$options.methods && vm.$options.methods[value]
        node.addEventListener(eventName, fn.bind(vm), false)
    },
    updater:{
        textUpdata (node, value) {


            node.textContent = value
        },
        htmlUpdata (node, value) {
            node.innerHTML = value
        },
        modleUpdata (node, value) {
            node.value = value
        },
    }
}
class Compile {
    constructor (el, vm) {
        this.vm = vm
        // 判断是否是一个元素节点 如果是直接赋值 不是则获取值
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        // 因为每次匹配到进行替换是，会导致页面的回流和重绘，影响页面的的性能
        // 所以需要创建文档碎片来进行缓存，减少页面的回流和重绘
        console.log(this.el);
        let framgent = this.createFramgent(this.el)

        // 开始进行模板的编译
        this.compile(framgent)

        // 再将文档碎片添加到根元素中然后渲染到页面
        this.el.appendChild(framgent)
    }
    compile (framgent) {
        const nodes = framgent.childNodes;
        // console.log(childNodes)
        // 遍历全部的节点并判断是元素节点还是文本节点
        // 将伪数组转为真数组
        // const childNodesArray = Array.from(childNodes)
        [...nodes].forEach(node => {
            if(this.isElementNode(node)){
                // 是元素节点
                // console.log(node);
                this.compileElement(node)
            } else {
                //是文本节点
                // console.log(node);
                this.compileText(node)
            }
            // 多层嵌套需要递归 子元素
            if(node.childNodes && node.childNodes.length){
                this.compile(node)
            }
        })
    }
    // 解析编译元素节点
    compileElement (elementNode) {
        // 编译元素 通过attributes获取元素节点的属性 里面包含name 和 value name为属性名字 value为属性值
        const attributes = elementNode.attributes;
        [...attributes].forEach(attr => {
            // name 属性名 v-text v-html  value 属性值 obj.name obj.age
            const {name, value} = attr
            if (this.isDirective(name)) {
                // 是指令
                // 解构 v-text v-html
                const [,directive] = name.split('-')
                const [dirName, eventName] = directive.split(':')
                // 是否存在这个指令对应的函数
                compileUtils[dirName] && compileUtils[dirName](elementNode, value, this.vm, eventName)
                // 依次标签中的属性
                elementNode.removeAttribute('v-' + directive)
            } else if (this.isEventName(name)) {
                // 是事件
                const [,eventName] = name.split('@')
                compileUtils['on'](elementNode, value, this.vm, eventName)
            }
        });
    }
    // 是否是指令
    isDirective (name) {
        // 以v-开头
        return name.startsWith('v-')
    }
    // 是否是事件
    isEventName (name) {
        // 以@开头
        return name.startsWith('@')
    }
    // 解析编译文本节点
    compileText (textNode) {
        // 编译文本
        // 获取文本内容
        const content = textNode.textContent
        // 正则匹配
        const reg = /\{\{(.+?)\}\}/
        if(reg.test(content)) {
            compileUtils['text'](textNode, content, this.vm)
        }
    }
    // 创建文档碎片
    createFramgent (node) {
        const framgent = document.createDocumentFragment(node)
        // 循环依次将节点添加到文档碎片中 firstChild 包含空格换行符
        // console.log(node.firstChild);
        let children
        while (children = node.firstChild) {
            // 依次追加当文档碎片中
            framgent.appendChild(children)
        }
        return framgent
    }
    isElementNode (node) {
        // node.nodeType 等于1 是元素节点 等于3是文本节点
        return node.nodeType === 1
    }
}
